#include "CentralCache.h"
#include "PageCache.h"

CentralCache CentralCache::_sInst;

// 从中心缓存的指定 SpanList 中获取一个包含可用内存块的 Span
Span* CentralCache::GetOneSpan(SpanList& list, size_t size)
{
	// 1.查看当前的spanlist中是否有还有未分配对象的span
	Span* it = list.Begin();
	while(it != list.End())
	{
		if(it->_freeList != nullptr)
		{
			return it;// 找到有可用内存块的span，直接返回
		}
		else
		{
			it = it->_next;// 继续遍历下一个span
		}
	}

	//2. 释放当前 SpanList 的锁（关键优化）
	// 先把central cache的桶锁解掉，避免阻塞其他线程释放内存
	//允许其他线程对该 SpanList 进行操作（例如，其他线程可能正在释放内存块到这个 
	//SpanList 中），减少锁竞争，提升并发性能
	list._mtx.unlock();

	//3. 向 PageCache 申请新的 Span
	PageCache::GetInstance()->_pageMtx.lock();// 加PageCache的全局锁
	Span* span = PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(size));
	span->_isUse = true;
	span->_objSize = size;
	PageCache::GetInstance()->_pageMtx.unlock();// 释放锁

	//4. 将新申请的 Span 拆分为小内存块

	// 对获取span进行切分，不需要加锁，因为这会其他线程访问不到这个span
	//获取到 PageCache 的 Span 后，需要将其管理的大块内存（连续物理页）
	//拆分为多个 size 大小的小内存块，形成 _freeList 链表

	// 计算span管理的大块内存的起始地址和总大小（字节）
	char* start = (char*)(span->_pageId << PAGE_SHIFT);// 起始地址 = 页ID × 页大小
	size_t bytes = span->_n * PAGE_SHIFT; // 总大小 = 页数 × 页大小
	char* end = start + bytes; // 结束地址 = 起始地址 + 总大小

	// 拆分大块内存为size大小的内存块，用_freeList链表连接
	span->_freeList = start;// 第一个内存块作为链表头
	start += size;// 移动到下一个内存块的起始位置
	void* tail = span->_freeList;// 尾指针，用于链表连接

	// 循环拆分剩余内存
	//将连续的大块内存按 size 大小切割，通过 NextObj
	//在每个内存块的头部记录下一个内存块的地址，形成隐式链表（无需额外的链表节点结构，节省内存）
	int i = 1;
	while(start < end)
	{
		i++;
		NextObj(tail) = start;// 当前尾节点的next指向新内存块
		tail = NextObj(tail);// 尾指针后移到新节点
		start += size;// 移动到下一个内存块的起始位置
	}
	NextObj(tail) = nullptr;  // 链表尾部置空

	//5. 将拆分好的 Span 加入 SpanList 并返回
	// 加锁后将span加入当前SpanList
	list._mtx.lock();
	list.PushFront(span);  // 插入链表头部

	return span;  // 返回包含可用内存块的span
}

// 从中心缓存获取一定数量的对象给thread cache
size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{
	// 1.计算内存块对应的索引
	size_t index = SizeClass::Index(size);
	// 2.加锁保证线程安全
	_spanLists[index]._mtx.lock();
	// 3.从中心缓存中获取一定数量的内存块
	Span* span = GetOneSpan(_spanLists[index], size);
	assert(span);
	assert(span->_freeList);
	// 4.从 Span 中拆分出批量内存块
	// 如果不够batchNum个，有多少拿多少
	start = span->_freeList;
	end = start;
	size_t i = 0;
	size_t actualNum = 1;// 至少有一个对象

	// 最多获取 batchNum 个对象，若不足则有多少拿多少
	while(i < batchNum - 1 && NextObj(end) != nullptr)
	{
		end = NextObj(end);
		i++;
		actualNum++;
	}

	// 5.更新 Span 的状态
	// 从 Span 的 _freeList 中拆分出最多 batchNum 个内存对象，形成一个独立的链表
	span->_freeList = NextObj(end);// Span 剩余的内存块链表头更新为 end 的下一个对象
	NextObj(end) = nullptr;  // 当前获取的链表尾部置空，避免与 Span 剩余内存块关联
	span->_useCount += actualNum;// 记录实际获取的对象数量

	// 6.解锁并返回实际获取的数量
	_spanLists[index]._mtx.unlock();
	return actualNum;
}

//中心缓存（CentralCache）中将一批内存块归还给其所属的内存页（Span）
//并在合适时将完整的内存页归还给页缓存（PageCache)
void CentralCache::ReleaseListToSpans(void* start, size_t size)
{
	//计算对应桶的索引
	size_t index = SizeClass::Index(size);
	//CentralCache是多线程共享的缓存层，操作它的_spanLists需要加锁
	_spanLists[index]._mtx.lock();

	//遍历所有需要归还的内存块，找到它们所属的Span管理结构
	while(start)
	{
		void* next = NextObj(start);

		//找到start指向的对象所属的Span管理结构
		//MapObjectToSpan：根据对象的内存地址（start）
		//反向查找它属于哪个Span（类似 “地址→管理单元” 的映射）
		Span* span = PageCache::GetInstance()->MapObjectToSpan(start);
		//将start指向的内存块加入到Span的空闲列表中（作为新的空闲块）
		NextObj(start) = span->_freeList;
		span->_freeList = start;
		span->_useCount--;

		// 说明span的切分出去的所有小块内存都回来了
		// 这个span就可以再回收给page cache，pagecache可以再尝试去做前后页的合并
		if (span->_useCount == 0)
		{
			_spanLists[index].Erase(span);
			//重置 Span 的状态,准备归还给PageCache
			span->_freeList = nullptr;
			span->_next = nullptr;
			span->_prev = nullptr;

			// 释放span给page cache时，使用page cache的锁就可以了
			// 这时把桶锁解掉
			_spanLists[index]._mtx.unlock();

			PageCache::GetInstance()->_pageMtx.lock();
			PageCache::GetInstance()->ReleaseSpanToPageCache(span);
			PageCache::GetInstance()->_pageMtx.unlock();

			//重新获取 CentralCache 的桶锁
			_spanLists[index]._mtx.lock();
		}
		start = next;
	}
	//所有内存块处理完毕后，释放该桶的锁
	_spanLists[index]._mtx.unlock();
}

